﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using Microsoft.Xna.Framework.Graphics;

namespace Tomahawk.Runtime.Rendering.Materials
{
    public sealed class SASScript
    {
        #region Script Context
        public struct Context
        {
            public SASRenderEnvironment sasEnvironment;
            public SASEffect sasEffect;
            public SASTechnique sasTechnique;
            public SASPass sasPass;

            public PrimitiveType primitiveType;
            public int baseVertex;
            public int numVertices;
            public bool indexedVertices;
            public int startIndex;
            public int primitiveCount;

            public GraphicsDevice device;

            private static Context emptyContext = new Context()
                {
                    sasEnvironment = null,
                    sasEffect = null,
                    sasTechnique = null,
                    sasPass = null,
                    primitiveType = PrimitiveType.TriangleList,
                    baseVertex = 0,
                    numVertices = 0,
                    indexedVertices = true,
                    startIndex = 0,
                    primitiveCount = 0,
                    device = null
                };
            public static Context EmptyContext { get { return emptyContext; } }
        }
        #endregion

        #region Commands
        #region SASScriptCommand
        public abstract class SASScriptCommand
        {
            protected string value;
            public SASScriptCommand(string value)
            {
                this.value = value;
            }
            public abstract bool Execute(ref Context context);
        }
        #endregion

        #region SASScriptGroupCommand  
        public class SASScriptGroupCommand : SASScriptCommand
        {
            public List<SASScriptCommand> commands = new List<SASScriptCommand>();

            public SASScriptGroupCommand(string value)
                : base(value)
            {
            }

            public override bool Execute(ref Context context)
            {
                bool result = true;

                foreach (SASScriptCommand command in commands)
                {
                    #if DEBUG_SAS_SCRIPTS
                    Console.WriteLine(command.ToString());
                    #endif

                    result &= command.Execute(ref context);
                }
                return true;
            }
        }
        #endregion

        #region SASScriptLoopCommand
        public class SASScriptLoopCommand : SASScriptGroupCommand
        {
            private SASParameter sasParameter = null;
            public SASScriptLoopCommand(string value)
                : base(value)
            {
            }
            public override bool Execute(ref Context context)
            {
                if (context.sasEffect == null)
                {
                    return false;
                }

                if (sasParameter == null)
                {
                    sasParameter = context.sasEffect.Parameters[value];
                }

                if (sasParameter != null)
                {
                    bool result = true;
                    int steps = 0;
                    switch (sasParameter.Type)
                    {
                        case SASParameter.SASParameterType.SASBool:
                            bool boolValue;
                            sasParameter.GetBoolValue(out boolValue);
                            steps = boolValue ? 1 : 0;
                            break;
                        case SASParameter.SASParameterType.SASInt:
                            int intValue;
                            sasParameter.GetIntValue(out intValue);
                            steps = intValue;
                            break;
                        case SASParameter.SASParameterType.SASFloat:
                            float floatValue;
                            sasParameter.GetFloatValue(out floatValue);
                            steps = (int)Math.Round(floatValue);
                            break;
                    }
                    while (steps > 0)
                    {
                        result = base.Execute(ref context);
                        --steps;
                    }

                    return result;
                }

                return false;
            }
        }
        #endregion

        #region SASScriptTechniqueCommand
        public class SASScriptTechniqueCommand : SASScriptCommand
        {
            public SASScriptTechniqueCommand(string value)
                : base(value)
            {
            }
            public override bool Execute(ref Context context)
            {
                if (context.sasEffect == null)
                {
                    return false;
                }
                
                return context.sasEffect.SelectTechniques(value);
            }
        }
        #endregion

        #region SASScriptPassCommand
        public class SASScriptPassCommand : SASScriptCommand
        {
            private SASPass sasPass = null;
            public SASScriptPassCommand(string value) : base(value)
            {
            }
            public override bool Execute(ref Context context)
            {
                if (context.sasEffect == null || context.sasTechnique == null)
                {
                    return false;
                }

                bool result = true;

                if (sasPass == null)
                {
                    context.sasTechnique.PassTable.TryGetValue(value, out sasPass);
                }

                if (sasPass != null)
                {
                    context.sasPass = sasPass;
                    result = context.sasPass.DrawMesh(ref context);
                }

                return result;
            }
        }
        #endregion

        #region SASScriptClearSetColorCommand
        public class SASScriptClearSetColorCommand : SASScriptCommand
        {
            private SASParameter sasParameter = null;
            public SASScriptClearSetColorCommand(string value)
                : base(value)
            {
            }
            public override bool Execute(ref Context context)
            {
                if (context.sasEffect == null)
                {
                    return false;
                }
                
                bool result = false;
                
                if (sasParameter == null)
                {
                    sasParameter = context.sasEffect.Parameters[value];
                }

                if (sasParameter != null)
                {
                    Color color;
                    sasParameter.GetColorValue(out color);
                    Engine.Instance.ClearColor = color;
                    result = true;
                }
                return result;
            }
        }
        #endregion

        #region SASScriptClearSetDepthCommand
        public class SASScriptClearSetDepthCommand : SASScriptCommand
        {
            private SASParameter sasParameter = null;
            public SASScriptClearSetDepthCommand(string value)
                : base(value)
            {
            }
            public override bool Execute(ref Context context)
            {
                if (context.sasEffect == null)
                {
                    return false;
                }
                
                bool result = false;
                if (sasParameter == null)
                {
                    sasParameter = context.sasEffect.Parameters[value];
                }

                if (sasParameter != null)
                {
                    float depth;
                    sasParameter.GetFloatValue(out depth);
                    Engine.Instance.ClearDepth = depth;
                    result = true;
                }
                return result;
            }
        }
        #endregion

        #region SASScriptClearCommand
        public class SASScriptClearCommand : SASScriptCommand
        {
            public enum ClearMode
            {
                Color,
                Depth
            }

            protected ClearMode mode;

            public SASScriptClearCommand(string value)
                : base(value)
            {
                if ((string.Compare(this.value, "COLOR", true) == 0) ||
                    (string.Compare(this.value, "COLOR0", true) == 0) ||
                    (string.Compare(this.value, "COLOR1", true) == 0) ||
                    (string.Compare(this.value, "COLOR2", true) == 0))
                {
                    mode = ClearMode.Color;
                }
                else if (string.Compare(this.value, "DEPTH", true) == 0)
                {
                    mode = ClearMode.Depth;
                }
            }
            public override bool Execute(ref Context context)
            {
                if (context.device == null)
                {
                    return false;
                }

                bool result = true;

                ClearOptions options = 0;
                switch (mode)
                {
                    case ClearMode.Color: options |= ClearOptions.Target; break;
                    case ClearMode.Depth: options |= ClearOptions.DepthBuffer; break;
                }

                context.device.Clear(options, Engine.Instance.ClearColor, Engine.Instance.ClearDepth, 0);
                return result;
            }
        }
        #endregion

        #region SASScriptDepthStencilTargetCommand
        public class SASScriptDepthStencilTargetCommand : SASScriptCommand
        {
            public SASScriptDepthStencilTargetCommand(string value)
                : base(value)
            {
            }
            public override bool Execute(ref Context context)
            {
                if (context.device == null || context.sasEffect == null ||
                    !(context.sasEffect is SASSceneEffect))
                {
                    return false;
                }

                bool result = true;

                result = ((SASSceneEffect)context.sasEffect).ActivateDepthStencilBuffer(value, context.device);

                return result;
            }
        }
        #endregion

        #region SASScriptRenderColorTargetCommand
        public class SASScriptRenderColorTargetCommand : SASScriptCommand
        {
            protected int index = 0;

            public SASScriptRenderColorTargetCommand(int index, string value)
                : base(value)
            {
                this.index = index;
            }
            public override bool Execute(ref Context context)
            {
                if (context.device == null || context.sasEffect == null ||
                    !(context.sasEffect is SASSceneEffect))
                {
                    return false;
                }

                bool result = true;

                result = ((SASSceneEffect) context.sasEffect).ActivateRenderTarget(index, value, context.device);

                return result;
            }
        }
        #endregion

        #region SASScriptExternalCommand
        public class SASScriptExternalCommand : SASScriptCommand
        {
            public SASScriptExternalCommand(string value)
                : base(value)
            {
            }
            public override bool Execute(ref Context context)
            {
                if (context.device == null || context.sasEffect == null || !(context.sasEffect is SASSceneEffect))
                {
                    return false;
                }

                return context.sasEffect.DrawExternal(value, context.device);
            }
        }
        #endregion

        #region SASScriptDrawMeshCommand
        public class SASScriptDrawMeshCommand : SASScriptCommand
        {
            protected enum DrawMode
            {
                Geometry,
                Buffer,
                None
            }
            protected DrawMode drawMode;

            public SASScriptDrawMeshCommand(string value)
                : base(value)
            {
                if (string.Compare(this.value, "GEOMETRY", true) == 0)
                {
                    drawMode = DrawMode.Geometry;
                }
                else if (string.Compare(this.value, "BUFFER", true) == 0)
                {
                    drawMode = DrawMode.Buffer;
                }
                else if (string.Compare(this.value, "NONE", true) == 0)
                {
                    drawMode = DrawMode.None;
                }
            }

            public override bool Execute(ref Context context)
            {
                if (context.sasEffect == null || context.sasPass == null || context.device == null)
                {
                    return false;
                }

                bool result = true;
                SASEffect effect = context.sasEffect;
                EffectPass pass = context.sasPass.Pass;
                GraphicsDevice device = context.device;

                pass.Begin();

                switch (drawMode)
                {
                    case DrawMode.Geometry:
                        {
                            if (context.indexedVertices)
                            {
                                device.DrawIndexedPrimitives(
                                    context.primitiveType,
                                    context.baseVertex,
                                    0,
                                    context.numVertices,
                                    context.startIndex,
                                    context.primitiveCount);
                            }
                            else
                            {
                                device.DrawPrimitives(
                                    context.primitiveType,
                                    context.baseVertex,
                                    context.primitiveCount);
                            }
                        } break;
                    case DrawMode.Buffer:
                        {
                            FillMode previousMode = device.RenderState.FillMode;
                            if (previousMode != FillMode.Solid)
                            {
                                device.RenderState.FillMode = FillMode.Solid;
                            }

                            effect.DrawQuad(device);

                            if (previousMode != FillMode.Solid)
                            {
                                device.RenderState.FillMode = previousMode;
                            }
                        } break;
                }

                pass.End();

                return result;
            }
        }
        #endregion
        #endregion

        #region Properties
        private SASScriptGroupCommand main = new SASScriptGroupCommand("");
        #endregion

        public SASScript(string script)
        {
            Stack<SASScriptGroupCommand> commandStack = new Stack<SASScriptGroupCommand>();
            commandStack.Push(main);

            string[] lines = script.Split(new char[] { ';' });
            foreach (string line in lines)
            {
                string[] tokens = line.Split(new char[] { '=' });
                if (tokens.Count() == 2)
                {
                    string type = tokens[0];
                    string value = tokens[1];

                    if (string.Compare(type, "CLEAR", true) == 0)
                    {
                        SASScriptClearCommand command = new SASScriptClearCommand(value);
                        commandStack.Peek().commands.Add(command);
                    }
                    else if (string.Compare(type, "CLEARSETCOLOR", true) == 0)
                    {
                        SASScriptClearSetColorCommand command = new SASScriptClearSetColorCommand(value);
                        commandStack.Peek().commands.Add(command);
                    }
                    else if (string.Compare(type, "CLEARSETDEPTH", true) == 0)
                    {
                        SASScriptClearSetDepthCommand command = new SASScriptClearSetDepthCommand(value);
                        commandStack.Peek().commands.Add(command);
                    }
                    else if (string.Compare(type, "RENDERDEPTHSTENCILTARGET", true) == 0)
                    {
                        SASScriptDepthStencilTargetCommand command = new SASScriptDepthStencilTargetCommand(value);
                        commandStack.Peek().commands.Add(command);
                    }
                    else if ((string.Compare(type, "RENDERCOLORTARGET", true) == 0) ||
                        (string.Compare(type, "RENDERCOLORTARGET0", true) == 0))
                    {
                        SASScriptRenderColorTargetCommand command = new SASScriptRenderColorTargetCommand(0, value);
                        commandStack.Peek().commands.Add(command);
                    }
                    else if (string.Compare(type, "RENDERCOLORTARGET1", true) == 0)
                    {
                        SASScriptRenderColorTargetCommand command = new SASScriptRenderColorTargetCommand(1, value);
                        commandStack.Peek().commands.Add(command);
                    }
                    else if (string.Compare(type, "RENDERCOLORTARGET2", true) == 0)
                    {
                        SASScriptRenderColorTargetCommand command = new SASScriptRenderColorTargetCommand(2, value);
                        commandStack.Peek().commands.Add(command);
                    }
                    else if (string.Compare(type, "SCRIPTEXTERNAL", true) == 0)
                    {
                        SASScriptExternalCommand command = new SASScriptExternalCommand(value);
                        commandStack.Peek().commands.Add(command);
                    }
                    else if (string.Compare(type, "DRAW", true) == 0)
                    {
                        SASScriptDrawMeshCommand command = new SASScriptDrawMeshCommand(value);
                        commandStack.Peek().commands.Add(command);
                    }
                    else if (string.Compare(type, "TECHNIQUE", true) == 0)
                    {
                        SASScriptTechniqueCommand command = new SASScriptTechniqueCommand(value);
                        commandStack.Peek().commands.Add(command);
                    }
                    else if (string.Compare(type, "PASS", true) == 0)
                    {
                        SASScriptPassCommand command = new SASScriptPassCommand(value);
                        commandStack.Peek().commands.Add(command);
                    }
                    else if (string.Compare(type, "LOOPBYCOUNT", true) == 0)
                    {
                        SASScriptLoopCommand command = new SASScriptLoopCommand(value);
                        commandStack.Peek().commands.Add(command);
                        commandStack.Push(command);
                    }
                    else if (string.Compare(type, "LOOPEND", true) == 0)
                    {
                        commandStack.Pop();
                    }
                }
            }
        }

        public bool Execute(ref Context context)
        {
            #if DEBUG_SAS_SCRIPTS
            Console.WriteLine("---------------------------------------");
            #endif

            return main.Execute(ref context);
        }
    }
}
